package com.eastedu.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.eastedu.model.permission.common.DownloadItem;
import com.eastedu.model.permission.common.DownloadPermission;
import com.eastedu.model.permission.common.ResourceType;
import com.eastedu.model.permission.common.ResourceTypeEnum;
import com.eastedu.model.permission.dto.GrowingResourcePermissionDTO;
import com.eastedu.repository.permission.po.BaseEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 〈主要是对权限数据的抽象〉
 *
 * @author ZHISHUAI.ZHANG
 * @since 2021/7/28
 */
@Slf4j
public abstract class AbstractPermissionDataSourceService<T extends BaseEntity> extends AbstractDataSyncService<T> {

    public AbstractPermissionDataSourceService(BaseMapper<T> mapper) {
        super(mapper);
    }

    @Override
    protected void updateContent(T po) {
        final String content = getContent(po);
        if (StringUtils.isNotBlank(content)) {
            GrowingResourcePermissionDTO dto = JSONObject.parseObject(content, GrowingResourcePermissionDTO.class);
            List<DownloadItem> items = getDownloadItems(dto.getResourceTypes());
            // 如果已经有下载权限，就不重复设置
            // 不在控制范围内的资源，也不用设置
            if (CollectionUtil.isNotEmpty(items)) {
                DownloadPermission downloadPermission = new DownloadPermission();
                downloadPermission.setDownloadControlCycle("ACADEMIC_YEAR");
                downloadPermission.setDownloadItems(items);
                dto.setDownloadPermission(downloadPermission);
            }
            // 将【课件】更名为【教学课件】
            updateCoursewareName(dto);
            removeDownloadPermission(dto);
            setContent(po, JSONObject.toJSONString(dto));
        }
    }

    private void removeDownloadPermission(GrowingResourcePermissionDTO dto) {
        if (CollUtil.isEmpty(dto.getResourceTypes())) {
            return;
        }
        dto.getResourceTypes().forEach(resourceType -> {
            resourceType.setFunctionPermissions(null);
            List<ResourceType> relationships = resourceType.getRelationships();
            if (CollUtil.isNotEmpty(relationships)) {
                relationships.forEach(relationship -> relationship.setFunctionPermissions(null));
            }
        });
    }

    private void updateCoursewareName(GrowingResourcePermissionDTO dto) {
        if (CollUtil.isEmpty(dto.getResourceTypes())) {
            return;
        }
        dto.getResourceTypes().forEach(resourceType -> {
            if (ResourceTypeEnum.COURSEWARE.getCode().equals(resourceType.getId())) {
                resourceType.setName("教学课件");
            }
            List<ResourceType> relationships = resourceType.getRelationships();
            if (CollUtil.isNotEmpty(relationships)) {
                relationships.forEach(relationship -> {
                    if (ResourceTypeEnum.COURSEWARE.getCode().equals(relationship.getId())) {
                        relationship.setName("教学课件");
                    }
                });
            }
        });
    }

    private List<ResourceTypeEnum> getSortedResourceTypes() {
        return ResourceTypeEnum.sortedResourceTypes();
    }

    private List<DownloadItem> getDownloadItems(List<ResourceType> resourceTypes) {

        if (CollUtil.isEmpty(resourceTypes)) {
            return Collections.emptyList();
        }
        List<ResourceTypeEnum> resourceTypeEnumList = Lists.newArrayList();

        List<String> previewRules = getPreviewRules(resourceTypes);
        if (previewRules.contains(ResourceTypeEnum.PREVIEW.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.PREVIEW);
        }
        List<String> assignmentRules = getAssignmentRules(resourceTypes);
        if (assignmentRules.contains(ResourceTypeEnum.ASSIGNMENT.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.ASSIGNMENT);
        }
        List<String> paperRules = getPaperRules(resourceTypes);
        if (paperRules.contains(ResourceTypeEnum.PAPER.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.PAPER);
        }
        List<String> coursewareRules = getCoursewareRules(resourceTypes);
        if (coursewareRules.contains(ResourceTypeEnum.COURSEWARE.getCode())
                || coursewareRules.contains(ResourceTypeEnum.PREPARATION.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.COURSEWARE);
        }
        List<String> teachingResearchWareRules = getTeachingResearchWareRules(resourceTypes);
        if (teachingResearchWareRules.contains(ResourceTypeEnum.TEACHING_RESEARCH_WARE.getCode())
                || teachingResearchWareRules.contains(ResourceTypeEnum.TEACHING_RESEARCH_RECORD.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.TEACHING_RESEARCH_WARE);
        }
        List<String> prepareLessonWareRules = getPrepareLessonWareRules(resourceTypes);
        if (prepareLessonWareRules.contains(ResourceTypeEnum.PREPARE_LESSON_WARE.getCode())
                || prepareLessonWareRules.contains(ResourceTypeEnum.CLASS_MEMOIR.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.PREPARE_LESSON_WARE);
        }
        List<String> teachingCaseRules = getTeachingCaseRules(resourceTypes);
        if (teachingCaseRules.contains(ResourceTypeEnum.TEACHING_CASE.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.TEACHING_CASE);
        }
        List<String> generalMemoirRules = getGeneralMemoirRules(resourceTypes);
        if (generalMemoirRules.contains(ResourceTypeEnum.GENERAL_MEMOIR.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.GENERAL_MEMOIR);
        }
        List<String> modelLessonRules = getModelLessonRules(resourceTypes);
        if (modelLessonRules.contains(ResourceTypeEnum.MODEL_LESSON.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.MODEL_LESSON);
        }
        List<String> activityPlanRules = getActivityPlanRules(resourceTypes);
        if (activityPlanRules.contains(ResourceTypeEnum.ACTIVITY_PLAN.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.ACTIVITY_PLAN);
        }
        List<String> studentSampleRules = getStudentSampleRules(resourceTypes);
        if (studentSampleRules.contains(ResourceTypeEnum.STUDENT_SAMPLE.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.STUDENT_SAMPLE);
        }
        List<String> extracurricularExtensionRules = getExtracurricularExtensionRules(resourceTypes);
        if (extracurricularExtensionRules.contains(ResourceTypeEnum.EXTRACURRICULAR_EXTENSION.getCode())) {
            resourceTypeEnumList.add(ResourceTypeEnum.EXTRACURRICULAR_EXTENSION);
        }

        List<ResourceTypeEnum> sortedResourceTypes = getSortedResourceTypes();
        return resourceTypeEnumList.stream()
                .sorted(Comparator.comparingInt(sortedResourceTypes::indexOf))
                .map(resourceType -> new DownloadItem(resourceType.getCode(), resourceType.getName(), null))
                .collect(Collectors.toList());
    }

    private List<String> getExtracurricularExtensionRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(
                ResourceTypeEnum.ACTIVITY_RECORD,
                ResourceTypeEnum.ACTIVITY_PLAN
        ));
    }

    private List<String> getStudentSampleRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(
                ResourceTypeEnum.ACTIVITY_RECORD,
                ResourceTypeEnum.ACTIVITY_PLAN
        ));
    }

    private List<String> getActivityPlanRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(
                ResourceTypeEnum.ACTIVITY_RECORD
        ));
    }

    private List<String> getModelLessonRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(
                ResourceTypeEnum.PREPARATION
        ), resourceType -> CollUtil.isNotEmpty(resourceType.getFunctionPermissions()));
    }

    private List<String> getGeneralMemoirRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(
                ResourceTypeEnum.PREPARATION
        ), resourceType -> CollUtil.isNotEmpty(resourceType.getFunctionPermissions()));
    }

    private List<String> getTeachingCaseRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(
                ResourceTypeEnum.PREPARATION,
                ResourceTypeEnum.MODEL_LESSON,
                ResourceTypeEnum.GENERAL_MEMOIR,
                ResourceTypeEnum.THEME_LESSON
        ));
    }

    private List<String> getPrepareLessonWareRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(ResourceTypeEnum.PREPARATION));
    }

    private List<String> getTeachingResearchWareRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes);
    }

    private List<String> getCoursewareRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(
                ResourceTypeEnum.MODEL_LESSON,
                ResourceTypeEnum.GENERAL_MEMOIR,
                ResourceTypeEnum.THEME_LESSON
        ));
    }

    private List<String> getPaperRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(
                ResourceTypeEnum.PREPARATION,
                ResourceTypeEnum.MODEL_LESSON,
                ResourceTypeEnum.GENERAL_MEMOIR,
                ResourceTypeEnum.THEME_LESSON
        ));
    }

    private List<String> getAssignmentRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(
                ResourceTypeEnum.PREPARATION,
                ResourceTypeEnum.MODEL_LESSON,
                ResourceTypeEnum.GENERAL_MEMOIR,
                ResourceTypeEnum.THEME_LESSON
        ));
    }

    private List<String> getPreviewRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, ListUtil.toList(
                ResourceTypeEnum.PREPARATION,
                ResourceTypeEnum.MODEL_LESSON,
                ResourceTypeEnum.GENERAL_MEMOIR,
                ResourceTypeEnum.THEME_LESSON
        ));
    }

    private List<String> getRules(List<ResourceType> resourceTypes) {
        return getRules(resourceTypes, null);
    }

    private List<String> getRules(List<ResourceType> resourceTypes, List<ResourceTypeEnum> predefinedResourceTypes) {
        return getRules(resourceTypes, predefinedResourceTypes, Objects::nonNull);
    }

    private List<String> getRules(List<ResourceType> resourceTypes, List<ResourceTypeEnum> predefinedResourceTypes,
                                  Predicate<ResourceType> predicate) {
        List<String> resourceTypeIds = getResourceTypeIds(resourceTypes, predicate);
        if (CollUtil.isEmpty(predefinedResourceTypes)) {
            return resourceTypeIds;
        }
        List<String> relatedResourceIds = getRelatedResourceIds(resourceTypes, predefinedResourceTypes, predicate);
        return (List<String>) CollUtil.union(resourceTypeIds, relatedResourceIds);
    }

    private List<String> getRelatedResourceIds(List<ResourceType> resourceTypes, List<ResourceTypeEnum> predefinedResourceTypes,
                                               Predicate<ResourceType> predicate) {
        List<String> predefinedResourceTypeCodes = predefinedResourceTypes.stream()
                .map(ResourceTypeEnum::getCode).collect(Collectors.toList());
        return resourceTypes.stream()
                .filter(resourceType -> predefinedResourceTypeCodes.contains(resourceType.getId()))
                .map(ResourceType::getRelationships)
                .filter(CollectionUtil::isNotEmpty)
                .flatMap(Collection::stream)
                .filter(predicate)
                .map(ResourceType::getId)
                .distinct()
                .collect(Collectors.toList());
    }
    private List<String> getResourceTypeIds(List<ResourceType> resourceTypes, Predicate<ResourceType> predicate) {
        return resourceTypes.stream()
                .filter(predicate)
                .map(ResourceType::getId)
                .distinct()
                .collect(Collectors.toList());
    }

    /**
     * 更新权限内容
     *
     * @param po          实体
     * @param jsonContent 权限内容
     */
    protected abstract void setContent(T po, String jsonContent);

    /**
     * 获取权限内容
     *
     * @param po 实体
     * @return 权限内容
     */
    protected abstract String getContent(T po);
}